// Copyright (c) 2025. Tony Robalik.
// SPDX-License-Identifier: Apache-2.0
package com.autonomousapps.artifacts

import com.autonomousapps.artifacts.utils.strings.camelCase
import org.gradle.api.Named
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.Category
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider

/**
 * Used for resolving custom artifacts in an aggregating project (often the "root" project), from producing projects
 * (often all or a subset of the subprojects ina build). Only for inter-project publishing and resolving (e.g., _not_
 * for publishing to Artifactory). See also [Publisher].
 *
 * Represents a set of tightly coupled [Configuration]s:
 * * A "dependency scope" configuration ([declarable]).
 * * A "resolvable" configuration ([internal]).
 * * A "consumable" configuration ([Publisher.external]).
 *
 * Dependencies are _declared_ on [declarable], and resolved within a project via [internal]. Custom artifacts (e.g.,
 * not jars), generated by tasks, are published via [Publisher.publish], which should be used on dependency
 * (artifact-producing) projects.
 *
 * Gradle uses [attributes][Attr] to wire the consumer project's [internal] (resolvable) configuration to the producer
 * project's [Publisher.external] (consumable) configuration, which is itself configured via [Publisher.publish].
 *
 * @see <a href="https://docs.gradle.org/current/userguide/cross_project_publications.html#sec:variant-aware-sharing">Variant-aware sharing of artifacts between projects</a>
 * @see <a href="https://dev.to/autonomousapps/configuration-roles-and-the-blogging-industrial-complex-21mn">Gradle configuration roles</a>
 */
public class Resolver<T : Named>(
  project: Project,
  declarableName: String,
  category: String,
  attr: Attr<T>,
) {

  public companion object {
    /** Convenience function for creating a [Resolver] for inter-project resolving of an [ArtifactDescription]. */
    public fun interProjectResolver(
      project: Project,
      artifactDescription: ArtifactDescription<*>,
    ): Resolver<out Named> {
      val artifactName = artifactDescription.name.camelCase()
      return Resolver(
        project = project,
        declarableName = artifactName,
        category = artifactDescription.categoryName,
        attr = Attr(artifactDescription.attribute, artifactName)
      )
    }
  }

  // Following the naming pattern established by the Java Library plugin. See
  // https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_configurations_graph
  private val internalName = "${declarableName}Classpath"

  /** Dependencies are declared on this configuration */
  public val declarable: NamedDomainObjectProvider<out Configuration> = project.dependencyScopeConfiguration(declarableName)

  /**
   * The plugin will resolve dependencies against this internal configuration, which extends from
   * the declared dependencies.
   */
  public val internal: NamedDomainObjectProvider<out Configuration> =
    project.resolvableConfiguration(internalName, declarable) { c ->
      // This attribute is identical to what is set on the external/consumable configuration
      c.attributes { attrs ->
        attrs.attribute(
          attr.attribute,
          project.objects.named(attr.attribute.type, attr.attributeName)
        )
        attrs.attribute(
          Category.CATEGORY_ATTRIBUTE,
          project.objects.named(Category::class.java, category),
        )
      }
    }

  /**
   * Maps this resolver to the files for all artifacts in this collection, ignoring missing artifacts. Used as a task
   * input like so:
   *
   * ```
   * // MyTask.kt
   * class MyTask : DefaultTask() {
   *
   *   @get:PathSensitive(PathSensitivity.RELATIVE)
   *   @get:InputFiles
   *   abstract val inputFiles: ConfigurableFileCollection
   * }
   * ```
   *
   * and
   *
   * ```
   * // MyPlugin.kt
   * project.tasks.register("myTask", MyTask::class.java) { t ->
   *   t.inputFiles.setFrom(resolver.artifactFilesProvider())
   * }
   * ```
   */
  public fun artifactFilesProvider(): Provider<FileCollection> =
    internal.map { c ->
      c.incoming.artifactView { view ->
        view.lenient(true)
      }.artifacts.artifactFiles
    }
}
